package com.neusoft.databus.agent.config;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.TimeUnit;

import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.google.common.eventbus.AsyncEventBus;
import com.neusoft.databus.common.bean.CommandBean;
import com.neusoft.databus.common.utils.DefaultThreadFactory;

@Aspect
@Configuration
public class DatabusAgentConfiguration {

    @Autowired
    private DatabusAgentProperties properties;
    @Value("${databus.agent.collect.asyncCollectMinThreadSize: 500}")
    private int asyncCollectMinThreadSize;
    @Value("${databus.agent.collect.asyncCollectMaxThreadSize: 3000}")
    private int asyncCollectMaxThreadSize;

    @SuppressWarnings("rawtypes")
    @Bean("socketPoolConfig")
    GenericKeyedObjectPoolConfig socketPoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getSocket().getMaxTotal());
        // can not be configured
        config.setMaxTotalPerKey(1);
        config.setBlockWhenExhausted(this.properties.getPool().getSocket().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getSocket().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getSocket().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getSocket().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(
                this.properties.getPool().getSocket().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getSocket().getMinEvictableIdleTimeMillis());
        return config;
    }

    @SuppressWarnings("rawtypes")
    @Bean("socket2PoolConfig")
    GenericKeyedObjectPoolConfig socket2PoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getSocket2().getMaxTotal());
        config.setMaxTotalPerKey(this.properties.getPool().getSocket2().getMaxTotalPerKey());
        config.setBlockWhenExhausted(this.properties.getPool().getSocket2().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getSocket2().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getSocket2().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getSocket2().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(
                this.properties.getPool().getSocket2().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getSocket2().getMinEvictableIdleTimeMillis());
        return config;
    }

    @SuppressWarnings("rawtypes")
    @Bean("serialPoolConfig")
    GenericKeyedObjectPoolConfig serialPoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getSerial().getMaxTotal());
        config.setMaxTotalPerKey(this.properties.getPool().getSerial().getMaxTotalPerKey());
        config.setBlockWhenExhausted(this.properties.getPool().getSerial().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getSocket().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getSerial().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getSerial().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(
                this.properties.getPool().getSerial().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getSerial().getMinEvictableIdleTimeMillis());
        return config;
    }

    @SuppressWarnings("rawtypes")
    @Bean("snmpPoolConfig")
    GenericKeyedObjectPoolConfig snmpPoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getSnmp().getMaxTotal());
        config.setMaxTotalPerKey(this.properties.getPool().getSnmp().getMaxTotalPerKey());
        config.setBlockWhenExhausted(this.properties.getPool().getSnmp().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getSocket().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getSnmp().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getSnmp().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(this.properties.getPool().getSnmp().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getSnmp().getMinEvictableIdleTimeMillis());
        return config;
    }

    @SuppressWarnings("rawtypes")
    @Bean("opcPoolConfig")
    GenericKeyedObjectPoolConfig opcPoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getOpc().getMaxTotal());
        config.setMaxTotalPerKey(this.properties.getPool().getOpc().getMaxTotalPerKey());
        config.setBlockWhenExhausted(this.properties.getPool().getOpc().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getOpc().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getOpc().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getOpc().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(this.properties.getPool().getOpc().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getOpc().getMinEvictableIdleTimeMillis());
        return config;
    }

    @SuppressWarnings("rawtypes")
    @Bean("nettyPoolConfig")
    GenericKeyedObjectPoolConfig nettyPoolConfig() {
        final GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig();
        config.setJmxEnabled(false);
        config.setMaxTotal(this.properties.getPool().getNetty().getMaxTotal());
        config.setMaxTotalPerKey(this.properties.getPool().getNetty().getMaxTotalPerKey());
        config.setBlockWhenExhausted(this.properties.getPool().getNetty().isBlockWhenExhausted());
        config.setMaxWaitMillis(this.properties.getPool().getNetty().getMaxWaitMillis());
        config.setTestOnCreate(this.properties.getPool().getNetty().isTestOnCreate());
        config.setTestOnBorrow(this.properties.getPool().getNetty().isTestOnBorrow());
        config.setTimeBetweenEvictionRunsMillis(
                this.properties.getPool().getNetty().getTimeBetweenEvictionRunsMillis());
        config.setMinEvictableIdleTimeMillis(this.properties.getPool().getNetty().getMinEvictableIdleTimeMillis());
        return config;
    }

    @Bean("asyncCollectionExecutorService")
    ExecutorService asyncCollectionExecutorService() {

        final ExecutorService executorService = new ThreadPoolExecutor(
                Integer.valueOf(this.asyncCollectMinThreadSize),
                Integer.valueOf(this.asyncCollectMaxThreadSize),
                0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                new DefaultThreadFactory("ac"),
                new AbortPolicy());

        return executorService;
    }

    /**
     * 定义切点Pointcut <br>
     * 第一个*号：表示返回类型， *号表示所有的类型 <br>
     * 第二个*号：表示类名，*号表示所有的类 <br>
     * 第三个*号：表示方法名，*号表示所有的方法 <br>
     * 后面括弧里面表示方法的参数，两个句点表示任何参数
     */
    @Pointcut("execution(*  com.neusoft.databus.agent.controller..*.collect(..))")
    public void executionService() {
    }

    @Before(value = "executionService()")
    public void doBefore(final JoinPoint joinPoint) {
        if ((joinPoint.getArgs().length > 0) && (joinPoint.getArgs()[0] instanceof CommandBean)) {
            final CommandBean commandBean = (CommandBean) joinPoint.getArgs()[0];
            final String traceId = commandBean.generateTraceId();
            MDC.put("trace", traceId);
        }
    }

    @AfterReturning(pointcut = "executionService()", returning = "returnValue")
    public void doAfterReturning(final JoinPoint joinPoint, final Object returnValue) {
        if ((joinPoint.getArgs().length > 0) && (joinPoint.getArgs()[0] instanceof CommandBean)) {
            MDC.clear();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public AsyncEventBus inboundEventBus() {
        return new AsyncEventBus(Executors.newCachedThreadPool());
    }

    //@Bean
    //public RedisTemplate<String, Object> redisTemplate() {
    //    final RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    //    redisTemplate.setConnectionFactory(this.redisConnectionFactory);
    //    redisTemplate.afterPropertiesSet();
    //    return redisTemplate;
    //}

    //@Bean
    //public RedisCacheWriter writer() {
    //    return RedisCacheWriter.nonLockingRedisCacheWriter(this.redisTemplate().getConnectionFactory());
    //}

    //@Bean
    //public CacheManager cacheManager() {
    //    final Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
    //    configurationMap.put(DatabusConstant.CACHE_NAME_RESOURCE,
    //            RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(this.resourceTTL)));
    //    return RedisCacheManager.builder(this.writer())
    //            .initialCacheNames(configurationMap.keySet())
    //            .withInitialCacheConfigurations(configurationMap)
    //            .build();
    //}
}
